package sandbox

import (
	"context"
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	// "github.com/docker/docker/api/types/image"
	"github.com/docker/docker/client"
)

type Result struct {
	Stdout string
}

var languages map[string][]string
var images map[string]string
var lastName map[string]string

// now support c++ , python
func init() {
	// 优化配置文件读取
	languages = make(map[string][]string)
	images = make(map[string]string)
	lastName = make(map[string]string)

	// awk 是这么好用
	//sudo docker stop $(sudo docker ps -a | grep gcc | awk '{print $1}')

	// 后面的g++ ... 相当于前面sh 的单参数，也就是说运行sh这个程序将后面参数依次放入这个程序
	languages["c++"] = []string{"sh", "-c", "g++ /app/code.cpp -o /app/a.out && ./app/a.out"}
	images["c++"] = "gcc:12"
	lastName["c++"] = "code.cpp"

	languages["python"] = []string{"sh", "-c", "python3 /app/code.py"}
	images["python"] = "python:latest"
	lastName["python"] = "code.py"

	// ctx := context.Background()
	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
	if err != nil {
		panic(err)
	}
	defer cli.Close()

//记得取消下面注释
// 	for _, imageName := range images {
// 		reader, err := cli.ImagePull(ctx, imageName, image.PullOptions{})
// 		if err != nil {
// 			fmt.Println("Pull images error", err)
// 			panic(1)
// 		}
// 		io.Copy(os.Stdout, reader)
// 	}
}

// Sandbox 用于暴露给外部调用接口

// 1 解析语言
// 2  运行容器
// 2.1 创建连接docker守护进程实例
// 2.2 将code写入文件中
// 2.3 创建容器并映射文件到docker
// 2.4 启动容器执行对应命令
// 2.5 创建exec组进行执行命令
// 2.6 执行attach接入到容器执行exec
// 3 返回结果

// Sandbox 学到： linux awt命令使用 看文档 docker知识
func sandbox(language string, code string) *Result {
	res := &Result{}
	ctx := context.Background()

	//1 解析语言
	language = strings.ToLower(language)
	if _, ok := languages[language]; !ok {
		return res
	}

	//2  运行容器
	//2.1 创建连接docker守护进程实例
	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
	if err != nil {
		panic(err)
	}
	defer cli.Close()

	// 2.2 将code写入文件中
	err = os.WriteFile("/home/dying/dockerCode/"+lastName[language], []byte(code), 0777)
	if err != nil {
		fmt.Println("Error writing to file:", err)
	}

	//2.3 创建容器并映射文件到docker
	con, err := cli.ContainerCreate(ctx,
		&container.Config{
			Tty:             true, //创建一个交互终端 防止一启动就关闭
			Image:           images[language],
			NetworkDisabled: true, //禁用网络
		},
		&container.HostConfig{
			AutoRemove: true,                                    //4.0 容器自动退出时删除
			Binds:      []string{"/home/dying/dockerCode:/app"}, //tnn 一个格式是这样 不加:就是匿名卷  弗拉弗拉
			Resources: container.Resources{
				Memory: 1024 * 1024 * 256, //内存限制256MB
			},
		}, nil, nil, "")

	if err != nil {
		fmt.Println("start container error", err)
		return res
	}

	// 2.4 启动容器执行对应命令
	err = cli.ContainerStart(ctx, con.ID, container.StartOptions{})
	if err != nil {
		fmt.Println("start container error", err)
		return res
	}
	// 2.5 创建exec组进行执行命令
	resp, err := cli.ContainerExecCreate(ctx, con.ID, types.ExecConfig{
		Cmd:          languages[language],
		AttachStdin:  true, //将docker容器和本地终端连接
		AttachStderr: true,
		AttachStdout: true,
		Tty:          true,
	})
	if err != nil {
		fmt.Println("ContainerExecCreate error", err)
		return res
	}
	// 2.6 执行attach接入到容器执行exec
	attachResp, err := cli.ContainerExecAttach(ctx, resp.ID, types.ExecStartCheck{})
	if err != nil {
		fmt.Println("ContainerExecAttach error", err)
		return res
	}

	//3 返回结果
	output, err := io.ReadAll(attachResp.Reader)
	if err != nil {
		fmt.Println("ReadAll error", err)
	}
	res.Stdout = string(output)
	// fmt.Println("result = " , string(output))

	//4 关闭容器并自动删除
	cli.ContainerStop(ctx, con.ID, container.StopOptions{})
	return res
}

func ExecuteCode(languags, code string) string {
	// Sandbox("c++", "#include<iostream> \n  using  namespace std;\n  int main()\n{ \ncout <<\"c++ hello world\";  \nreturn 0;\n}  ")
	//sandbox("python", "print(\"hello python\\n\") ")
	return sandbox(languags, code).Stdout

	// 拉取容容器（每个服务器第一次执行时需要操作）
	// 创建【交互】容器，并且绑定目录复制文件
	// 启动容器 、执行代码
	// 容器安全性：
	// 1 超时性 【头歌 让用户点击是否续时】【未解决】
	// 2 内存限制【设置最大内存】 【已解决 √】
	// 3 网路配置关闭【已解决 √】
	// 4 权限管理 docker已经做了系统层面隔离【比较安全 √】
}
